package com.lry.jvm.rtda.heap;

import com.lry.jvm.classfile.ClassFile;
import com.lry.jvm.rtda.JObject;
import com.lry.jvm.rtda.LocalVars;
import com.lry.jvm.util.Util;

import java.util.HashMap;
import java.util.Map;

public class JClass {
    //uint16
    private int accessFlag;

    private String name;
    private String superClassName;
    private String[] interfaceNames;
    private RuntimeConstantPool constantPool;

    private JField[] fields;
    private JMethod[] methods;
    private JClassLoader loader;
    private JClass superClass;
    private JClass[] interfaces;

    //实例变量占用空间大小
    private int instanceSlotCount;
    //类变量占用空间大小
    private int staticSlotCount;
    //存放静态变量
    private LocalVars staticVars;

    private ClassFile cf;

    //<clinit> 标识
    private boolean initStarted;

    private JObject jClass;//java.lang.Class 的实例

    public JClass(int accessFlag, String name, JClass superClass, JClassLoader loader, JClass[] interfaces, boolean initStarted) {
        this.accessFlag = accessFlag;
        this.name = name;
        this.superClass = superClass;
        this.loader = loader;
        this.interfaces = interfaces;
        this.initStarted = initStarted;
    }

    public JClass(ClassFile cf){
        this.cf = cf;
        this.accessFlag = cf.getAccessFlag();
        this.name = cf.className();
        this.superClassName = cf.superClassName();
        this.interfaceNames = cf.interfaceNames();
        this.constantPool = new RuntimeConstantPool(this,cf.getConstantPool());
        this.fields = JField.newJFields(this,cf.getFields());
        this.methods = JMethod.newJMethods(this,cf.getMethods());
    }
    public boolean isInterface(){
        return 0 != (accessFlag&AccessFlags.ACC_INTERFACE);
    }
    public boolean isSuper(){
        return 0 != (accessFlag&AccessFlags.ACC_SUPER);
    }
    public boolean isAbstract(){
        return 0 != (accessFlag&AccessFlags.ACC_ABSTRACT);
    }
    public boolean isPublic(){
        return 0 != (accessFlag&AccessFlags.ACC_PUBLIC);
    }

    public boolean isAccessibleTo(JClass other){
        return isPublic() || getPackageName().equals(other.getPackageName());
    }

    public JObject newArray(int len){
        if(!isArray()){
            Util.panic("Not array class:"+getName());
        }

        switch (getName()){
            //byte 和 boolean
            case "[Z":
            case "[B":
                return new JObject(this,new byte[len]);
            case "[C":
                return new JObject(this,new char[len]);
            case "[S":
                return new JObject(this,new short[len]);
            case "[I":
                return new JObject(this,new int[len]);
            case "[J":
                return new JObject(this,new long[len]);
            case "[F":
                return new JObject(this,new float[len]);
            case "[D":
                return new JObject(this,new double[len]);
            default:
                return new JObject(this,new JObject[len]);
        }
    }
    //返回数组类的元素类型
    public JClass componentClass(){
        String componentClassName = getComponentClassName(name);
        return loader.loadClass(componentClassName);
    }

    private String getComponentClassName(String name) {
        if(name.charAt(0)=='['){
            String componentTypeDescriptor = name.substring(1);
            return toClassName(componentTypeDescriptor);
        }
        Util.panic("Not array: " + name);
        return null;
    }

    private String toClassName(String descriptor) {
        //数组
       if(descriptor.charAt(0)=='['){
            return descriptor;
        }
        //Ljava/lang/System;
        if(descriptor.charAt(0)=='L'){
            return descriptor.substring(1,descriptor.length()-1);
        }
        for (Map.Entry<String, String> entry : primitiveTypes.entrySet()) {
            if(descriptor.equals(entry.getValue())){
                return entry.getKey();
            }
        }
        Util.panic("Invalid descriptor: " + descriptor);
        return null;
    }

    public boolean isArray() {
        return getName().charAt(0)=='[';
    }

    public String getPackageName(){
        int i = name.lastIndexOf("/");
        if(i>=0){
            return name.substring(i);
        }
        return "";
    }

    public JMethod getClinitMethod() {
        return getStaticMethod("<clinit>", "()V");
    }

    public JMethod getMainMethod() {
        return getStaticMethod("main", "([Ljava/lang/String;)V");
    }

    public JMethod getStaticMethod(String name, String descriptor){
        for(JMethod method: methods){
            if(method.isStatic()&&method.getName().equals(name)&&method.getDescriptor().equals(descriptor)){
                return method;
            }
        }
        return null;
    }


    public boolean isAssignableFrom(JClass other) {
        if(other==this){
            return true;
        }

        if(!other.isArray()){
            if(!other.isInterface()){
                if(!isInterface()){
                    return other.isSubClassOf(this);
                }else{
                    return other.isImplements(this);
                }
            }else{
                //别人是接口，自己是类
                if(!isInterface()){
                    return isJLObject();
                }else{
                    return other.isSubInterfaceOf(this);
                }
            }
        }else{
            //别人是数组，我也是数组
            if(isArray()){
                JClass otherClass = other.componentClass();
                JClass thisClass = componentClass();
                return otherClass == thisClass || thisClass.isAssignableFrom(otherClass);
            }
            //别人是数组，我不是数组
            else{
                if(!isInterface()){
                    return isJLObject();
                }else{
                    return isJlCloneable() || isJioSerializable();
                }
            }
        }
    }

    public boolean isJLObject(){
        return name.equals("java/lang/Object");
    }
    public boolean isJlCloneable(){
        return name.equals("java/lang/Cloneable");
    }
    public boolean isJioSerializable(){
        return name.equals("java/lang/Serializable");
    }
    //self extends other
    public boolean isSubClassOf(JClass other) {
        for(JClass c=superClass;c!=null;c=c.superClass){
            if(c==other){
                return true;
            }
        }
        return false;
    }

    //self implements other
    public boolean isImplements(JClass other){
        for(JClass c=this;c!=null;c=c.superClass){
            for(JClass in : c.interfaces){
                if(in==other || in.isSubInterfaceOf(other)){
                    return true;
                }
            }
        }
        return false;
    }

    private boolean isSubInterfaceOf(JClass other) {
        for(JClass in:interfaces){
            if(in==other||in.isSubInterfaceOf(other)){
                return true;
            }
        }
        return false;
    }

    public JObject getJClass() {
        return jClass;
    }

    public void setJClass(JObject jClass) {
        this.jClass = jClass;
    }

    public boolean isInitStarted() {
        return initStarted;
    }

    public void setInitStarted(boolean initStarted) {
        this.initStarted = initStarted;
    }

    public ClassFile getCf() {
        return cf;
    }

    public void setCf(ClassFile cf) {
        this.cf = cf;
    }

    public int getAccessFlag() {
        return accessFlag;
    }

    public void setAccessFlag(int accessFlag) {
        this.accessFlag = accessFlag;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSuperClassName() {
        return superClassName;
    }

    public void setSuperClassName(String superClassName) {
        this.superClassName = superClassName;
    }

    public String[] getInterfaceNames() {
        return interfaceNames;
    }

    public void setInterfaceNames(String[] interfaceNames) {
        this.interfaceNames = interfaceNames;
    }

    public RuntimeConstantPool getConstantPool() {
        return constantPool;
    }

    public void setConstantPool(RuntimeConstantPool constantPool) {
        this.constantPool = constantPool;
    }

    public JField[] getFields() {
        return fields;
    }

    public void setFields(JField[] fields) {
        this.fields = fields;
    }

    public JMethod[] getMethods() {
        return methods;
    }

    public void setMethods(JMethod[] methods) {
        this.methods = methods;
    }

    public JClassLoader getLoader() {
        return loader;
    }

    public void setLoader(JClassLoader loader) {
        this.loader = loader;
    }

    public JClass getSuperClass() {
        return superClass;
    }

    public void setSuperClass(JClass superClass) {
        this.superClass = superClass;
    }

    public JClass[] getInterfaces() {
        return interfaces;
    }

    public void setInterfaces(JClass[] interfaces) {
        this.interfaces = interfaces;
    }

    public int getInstanceSlotCount() {
        return instanceSlotCount;
    }

    public void setInstanceSlotCount(int instanceSlotCount) {
        this.instanceSlotCount = instanceSlotCount;
    }

    public int getStaticSlotCount() {
        return staticSlotCount;
    }

    public void setStaticSlotCount(int staticSlotCount) {
        this.staticSlotCount = staticSlotCount;
    }

    public LocalVars getStaticVars() {
        return staticVars;
    }

    public void setStaticVars(LocalVars staticVars) {
        this.staticVars = staticVars;
    }


    public JClass arrayClass() {
        String arrayClassName = getArrayClassName(name);
        return loader.loadClass(arrayClassName);
    }

    private String getArrayClassName(String name) {
        return "["+toDescriptor(name);
    }

    private String toDescriptor(String name) {
        if(name.charAt(0)=='['){
            return name;
        }
        String descriptor = primitiveTypes.get(name);
        if(null!=descriptor){
            return descriptor;
        }
        return "L" + name + ";";
    }
    public static Map<String,String> primitiveTypes = new HashMap();
    {
        primitiveTypes.put("void","V");
        primitiveTypes.put("boolean","Z");
        primitiveTypes.put("byte","B");
        primitiveTypes.put("short","S");
        primitiveTypes.put("int","I");
        primitiveTypes.put("long","J");
        primitiveTypes.put("char","C");
        primitiveTypes.put("float","F");
        primitiveTypes.put("double","D");
    }

    public JField getField(String name, String descriptor, boolean isStatic) {
        for(JClass c=this;c!=null;c=c.getSuperClass()){
            for(JField field:c.getFields()){
                if(isStatic==field.isStatic()&&
                field.getName().equals(name)&&
                field.getDescriptor().equals(descriptor)){
                    return field;
                }
            }
        }
        return null;
    }

    public String javaName() {
        return name.replace("/",".");
    }

}